1   /*
2    * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
3    * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4    *
5    * This code is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU General Public License version 2 only, as
7    * published by the Free Software Foundation.  Oracle designates this
8    * particular file as subject to the "Classpath" exception as provided
9    * by Oracle in the LICENSE file that accompanied this code.
10   *
11   * This code is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14   * version 2 for more details (a copy is included in the LICENSE file that
15   * accompanied this code).
16   *
17   * You should have received a copy of the GNU General Public License version
18   * 2 along with this work; if not, write to the Free Software Foundation,
19   * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20   *
21   * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22   * or visit www.oracle.com if you need additional information or have any
23   * questions.
24   */
25  
26  /*
27   *  (C) Copyright IBM Corp. 1999 All Rights Reserved.
28   *  Copyright 1997 The Open Group Research Institute.  All rights reserved.
29   */
30  
31  package sun.security.krb5.internal.tools;
32  
33  import sun.security.krb5.*;
34  import sun.security.krb5.internal.ktab.*;
35  import java.io.IOException;
36  import java.io.BufferedReader;
37  import java.io.InputStreamReader;
38  import java.io.File;
39  import java.text.DateFormat;
40  import java.util.Arrays;
41  import java.util.Date;
42  import java.util.Locale;
43  import sun.security.krb5.internal.crypto.EType;
44  /**
45   * This class can execute as a command-line tool to help the user manage
46   * entries in the key table.
47   * Available functions include list/add/update/delete service key(s).
48   *
49   * @author Yanni Zhang
50   * @author Ram Marti
51   */
52  
53  public class Ktab {
54      // KeyTabAdmin admin;
55      KeyTab table;
56      char action;
57      String name;   // name and directory of key table
58      String principal;
59      boolean showEType;
60      boolean showTime;
61      int etype = -1;
62      char[] password = null;
63  
64      boolean forced = false; // true if delete without prompt. Default false
65      boolean append = false; // true if new keys are appended. Default false
66      int vDel = -1;          // kvno to delete, -1 all, -2 old. Default -1
67      int vAdd = -1;          // kvno to add. Default -1, means auto incremented
68  
69      /**
70       * The main program that can be invoked at command line.
71       * See {@link #printHelp} for usages.
72       */
73      public static void main(String[] args) {
74          Ktab ktab = new Ktab();
75          if ((args.length == 1) && (args[0].equalsIgnoreCase("-help"))) {
76              ktab.printHelp();
77              return;
78          } else if ((args == null) || (args.length == 0)) {
79              ktab.action = 'l';
80          } else {
81              ktab.processArgs(args);
82          }
83          try {
84              if (ktab.name == null) {
85                  //  ktab.admin = new KeyTabAdmin();    // use the default keytab.
86                  ktab.table = KeyTab.getInstance();
87                  if (ktab.table == null) {
88                      if (ktab.action == 'a') {
89                          ktab.table = KeyTab.create();
90                      } else {
91                          System.out.println("No default key table exists.");
92                          System.exit(-1);
93                      }
94                  }
95              } else {
96                  if ((ktab.action != 'a') &&
97                      !(new File(ktab.name)).exists()) {
98                      System.out.println("Key table " +
99                                  ktab.name + " does not exist.");
100                     System.exit(-1);
101                 } else {
102                     ktab.table = KeyTab.getInstance(ktab.name);
103                 }
104                 if (ktab.table == null) {
105                     if (ktab.action == 'a') {
106                         ktab.table = KeyTab.create(ktab.name);
107                     } else {
108                         System.out.println("The format of key table " +
109                                 ktab.name + " is incorrect.");
110                         System.exit(-1);
111                     }
112                 }
113             }
114         } catch (RealmException e) {
115             System.err.println("Error loading key table.");
116             System.exit(-1);
117         } catch (IOException e) {
118             System.err.println("Error loading key table.");
119             System.exit(-1);
120         }
121         switch (ktab.action) {
122         case 'l':
123             ktab.listKt();
124             break;
125         case 'a':
126             ktab.addEntry();
127             break;
128         case 'd':
129             ktab.deleteEntry();
130             break;
131         default:
132             ktab.error("A command must be provided");
133         }
134     }
135 
136     /**
137      * Parses the command line arguments.
138      */
139     void processArgs(String[] args) {
140 
141         // Commands (should appear before options):
142         //   -l
143         //   -a <princ>
144         //   -d <princ>
145         // Options:
146         //   -e <etype> (for -d)
147         //   -e (for -l)
148         //   -n <kvno>
149         //   -k <keytab>
150         //   -t
151         //   -f
152         //   -append
153         // Optional extra arguments:
154         //   password for -a
155         //   [kvno|all|old] for -d
156 
157         boolean argAlreadyAppeared = false;
158         for (int i = 0; i < args.length; i++) {
159             if (args[i].startsWith("-")) {
160                 switch (args[i].toLowerCase(Locale.US)) {
161 
162                     // Commands
163                     case "-l":   // list
164                         action = 'l';
165                         break;
166                     case "-a":   // add a new entry to keytab.
167                         action = 'a';
168                         if (++i >= args.length || args[i].startsWith("-")) {
169                             error("A principal name must be specified after -a");
170                         }
171                         principal = args[i];
172                         break;
173                     case "-d":   // delete entries
174                         action = 'd';
175                         if (++i >= args.length || args[i].startsWith("-")) {
176                             error("A principal name must be specified after -d");
177                         }
178                         principal = args[i];
179                         break;
180 
181                         // Options
182                     case "-e":
183                         if (action == 'l') {    // list etypes
184                             showEType = true;
185                         } else if (action == 'd') { // delete etypes
186                             if (++i >= args.length || args[i].startsWith("-")) {
187                                 error("An etype must be specified after -e");
188                             }
189                             try {
190                                 etype = Integer.parseInt(args[i]);
191                                 if (etype <= 0) {
192                                     throw new NumberFormatException();
193                                 }
194                             } catch (NumberFormatException nfe) {
195                                 error(args[i] + " is not a valid etype");
196                             }
197                         } else {
198                             error(args[i] + " is not valid after -" + action);
199                         }
200                         break;
201                     case "-n":   // kvno for -a
202                         if (++i >= args.length || args[i].startsWith("-")) {
203                             error("A KVNO must be specified after -n");
204                         }
205                         try {
206                             vAdd = Integer.parseInt(args[i]);
207                             if (vAdd < 0) {
208                                 throw new NumberFormatException();
209                             }
210                         } catch (NumberFormatException nfe) {
211                             error(args[i] + " is not a valid KVNO");
212                         }
213                         break;
214                     case "-k":  // specify keytab to use
215                         if (++i >= args.length || args[i].startsWith("-")) {
216                             error("A keytab name must be specified after -k");
217                         }
218                         if (args[i].length() >= 5 &&
219                             args[i].substring(0, 5).equalsIgnoreCase("FILE:")) {
220                             name = args[i].substring(5);
221                         } else {
222                             name = args[i];
223                         }
224                         break;
225                     case "-t":   // list timestamps
226                         showTime = true;
227                         break;
228                     case "-f":   // force delete, no prompt
229                         forced = true;
230                         break;
231                     case "-append": // -a, new keys append to file
232                         append = true;
233                         break;
234                     default:
235                         error("Unknown command: " + args[i]);
236                         break;
237                 }
238             } else {    // optional standalone arguments
239                 if (argAlreadyAppeared) {
240                     error("Useless extra argument " + args[i]);
241                 }
242                 if (action == 'a') {
243                     password = args[i].toCharArray();
244                 } else if (action == 'd') {
245                     switch (args[i]) {
246                         case "all": vDel = -1; break;
247                         case "old": vDel = -2; break;
248                         default: {
249                             try {
250                                 vDel = Integer.parseInt(args[i]);
251                                 if (vDel < 0) {
252                                     throw new NumberFormatException();
253                                 }
254                             } catch (NumberFormatException nfe) {
255                                 error(args[i] + " is not a valid KVNO");
256                             }
257                         }
258                     }
259                 } else {
260                     error("Useless extra argument " + args[i]);
261                 }
262                 argAlreadyAppeared = true;
263             }
264         }
265     }
266 
267     /**
268      * Adds a service key to key table. If the specified key table does not
269      * exist, the program will automatically generate
270      * a new key table.
271      */
272     void addEntry() {
273         PrincipalName pname = null;
274         try {
275             pname = new PrincipalName(principal);
276             if (pname.getRealm() == null) {
277                 pname.setRealm(Config.getInstance().getDefaultRealm());
278             }
279         } catch (KrbException e) {
280             System.err.println("Failed to add " + principal +
281                                " to keytab.");
282             e.printStackTrace();
283             System.exit(-1);
284         }
285         if (password == null) {
286             try {
287                 BufferedReader cis =
288                     new BufferedReader(new InputStreamReader(System.in));
289                 System.out.print("Password for " + pname.toString() + ":");
290                 System.out.flush();
291                 password = cis.readLine().toCharArray();
292             } catch (IOException e) {
293                 System.err.println("Failed to read the password.");
294                 e.printStackTrace();
295                 System.exit(-1);
296             }
297 
298         }
299         try {
300             // admin.addEntry(pname, password);
301             table.addEntry(pname, password, vAdd, append);
302             Arrays.fill(password, '0');  // clear password
303             // admin.save();
304             table.save();
305             System.out.println("Done!");
306             System.out.println("Service key for " + principal +
307                                " is saved in " + table.tabName());
308 
309         } catch (KrbException e) {
310             System.err.println("Failed to add " + principal + " to keytab.");
311             e.printStackTrace();
312             System.exit(-1);
313         } catch (IOException e) {
314             System.err.println("Failed to save new entry.");
315             e.printStackTrace();
316             System.exit(-1);
317         }
318     }
319 
320     /**
321      * Lists key table name and entries in it.
322      */
323     void listKt() {
324         System.out.println("Keytab name: " + table.tabName());
325         KeyTabEntry[] entries = table.getEntries();
326         if ((entries != null) && (entries.length > 0)) {
327             String[][] output = new String[entries.length+1][showTime?3:2];
328             int column = 0;
329             output[0][column++] = "KVNO";
330             if (showTime) output[0][column++] = "Timestamp";
331             output[0][column++] = "Principal";
332             for (int i = 0; i < entries.length; i++) {
333                 column = 0;
334                 output[i+1][column++] = entries[i].getKey().
335                         getKeyVersionNumber().toString();
336                 if (showTime) output[i+1][column++] =
337                         DateFormat.getDateTimeInstance(
338                         DateFormat.SHORT, DateFormat.SHORT).format(
339                         new Date(entries[i].getTimeStamp().getTime()));
340                 String princ = entries[i].getService().toString();
341                 if (showEType) {
342                     int e = entries[i].getKey().getEType();
343                     output[i+1][column++] = princ + " (" + e + ":" +
344                             EType.toString(e) + ")";
345                 } else {
346                     output[i+1][column++] = princ;
347                 }
348             }
349             int[] width = new int[column];
350             for (int j=0; j<column; j++) {
351                 for (int i=0; i <= entries.length; i++) {
352                     if (output[i][j].length() > width[j]) {
353                         width[j] = output[i][j].length();
354                     }
355                 }
356                 if (j != 0) width[j] = -width[j];
357             }
358             for (int j=0; j<column; j++) {
359                 System.out.printf("%" + width[j] + "s ", output[0][j]);
360             }
361             System.out.println();
362             for (int j=0; j<column; j++) {
363                 for (int k=0; k<Math.abs(width[j]); k++) System.out.print("-");
364                 System.out.print(" ");
365             }
366             System.out.println();
367             for (int i=0; i<entries.length; i++) {
368                 for (int j=0; j<column; j++) {
369                     System.out.printf("%" + width[j] + "s ", output[i+1][j]);
370                 }
371                 System.out.println();
372             }
373         } else {
374             System.out.println("0 entry.");
375         }
376     }
377 
378     /**
379      * Deletes an entry from the key table.
380      */
381     void deleteEntry() {
382         PrincipalName pname = null;
383         try {
384             pname = new PrincipalName(principal);
385             if (pname.getRealm() == null) {
386                 pname.setRealm(Config.getInstance().getDefaultRealm());
387             }
388             if (!forced) {
389                 String answer;
390                 BufferedReader cis =
391                     new BufferedReader(new InputStreamReader(System.in));
392                 System.out.print("Are you sure you want to delete "+
393                         "service key(s) for " + pname.toString() +
394                         " (" + (etype==-1?"all etypes":("etype="+etype)) + ", " +
395                         (vDel==-1?"all kvno":(vDel==-2?"old kvno":("kvno=" + vDel))) +
396                         ") in " + table.tabName() + "? (Y/[N]): ");
397 
398                 System.out.flush();
399                 answer = cis.readLine();
400                 if (answer.equalsIgnoreCase("Y") ||
401                     answer.equalsIgnoreCase("Yes"));
402                 else {
403                     // no error, the user did not want to delete the entry
404                     System.exit(0);
405                 }
406             }
407         } catch (KrbException e) {
408             System.err.println("Error occured while deleting the entry. "+
409                                "Deletion failed.");
410             e.printStackTrace();
411             System.exit(-1);
412         } catch (IOException e) {
413             System.err.println("Error occured while deleting the entry. "+
414                                " Deletion failed.");
415             e.printStackTrace();
416             System.exit(-1);
417         }
418 
419         int count = table.deleteEntries(pname, etype, vDel);
420 
421         if (count == 0) {
422             System.err.println("No matched entry in the keytab. " +
423                                "Deletion fails.");
424             System.exit(-1);
425         } else {
426             try {
427                 table.save();
428             } catch (IOException e) {
429                 System.err.println("Error occurs while saving the keytab. " +
430                                    "Deletion fails.");
431                 e.printStackTrace();
432                 System.exit(-1);
433             }
434             System.out.println("Done! " + count + " entries removed.");
435         }
436     }
437 
438     void error(String... errors) {
439         for (String error: errors) {
440             System.out.println("Error: " + error + ".");
441         }
442         printHelp();
443         System.exit(-1);
444     }
445     /**
446      * Prints out the help information.
447      */
448     void printHelp() {
449         System.out.println("\nUsage: ktab <commands> <options>");
450         System.out.println();
451         System.out.println("Available commands:");
452         System.out.println();
453         System.out.println("-l [-e] [-t]\n"
454                 + "    list the keytab name and entries. -e with etype, -t with timestamp.");
455         System.out.println("-a <principal name> [<password>] [-n <kvno>] [-append]\n"
456                 + "    add new key entries to the keytab for the given principal name with\n"
457                 + "    optional <password>. If a <kvno> is specified, new keys' Key Version\n"
458                 + "    Numbers equal to the value, otherwise, automatically incrementing\n"
459                 + "    the Key Version Numbers. If -append is specified, new keys are\n"
460                 + "    appended to the keytab, otherwise, old keys for the\n"
461                 + "    same principal are removed.");
462         System.out.println("-d <principal name> [-f] [-e <etype>] [<kvno> | all | old]\n"
463                 + "    delete key entries from the keytab for the specified principal. If\n"
464                 + "    <kvno> is specified, delete keys whose Key Version Numbers match\n"
465                 + "    kvno. If \"all\" is specified, delete all keys. If \"old\" is specified,\n"
466                 + "    delete all keys except those with the highest kvno. Default action\n"
467                 + "    is \"all\". If <etype> is specified, only keys of this encryption type\n"
468                 + "    are deleted. <etype> should be specified as the numberic value etype\n"
469                 + "    defined in RFC 3961, section 8. A prompt to confirm the deletion is\n"
470                 + "    displayed unless -f is specified.");
471         System.out.println();
472         System.out.println("Common option(s):");
473         System.out.println();
474         System.out.println("-k <keytab name>\n"
475                 + "    specify keytab name and path with prefix FILE:");
476     }
477 }